// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdlib.h>
#include <zircon/status.h>

#include <utility>

#include <digest/digest.h>
#include <zxtest/zxtest.h>

// These unit tests are for the Digest object in ulib/digest.

namespace {

////////////////
// Test support.

using digest::Digest;

// echo -n | sha256sum
const char* kZeroDigest = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
// echo -n | sha256sum | cut -c1-64 | tr -d '\n' | xxd -p -r | sha256sum
const char* kDoubleZeroDigest = "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456";

////////////////
// Test cases

TEST(DigestTestCase, Strings) {
  Digest actual;
  ASSERT_OK(actual.Parse(kZeroDigest));
  char buf[(Digest::kLength * 2) + 1];
  ASSERT_OK(actual.ToString(buf, sizeof(buf)));
  EXPECT_STR_EQ(kZeroDigest, buf);
}

TEST(DigestTestCase, Zero) {
  Digest actual, expected;
  ASSERT_OK(expected.Parse(kZeroDigest));
  actual.Hash(nullptr, 0);
  EXPECT_BYTES_EQ(actual.get(), expected.get(), Digest::kLength);
}

TEST(DigestTestCase, Self) {
  Digest actual, expected;
  ASSERT_OK(expected.Parse(kDoubleZeroDigest));
  ASSERT_OK(actual.Parse(kZeroDigest));
  uint8_t buf[Digest::kLength];
  ASSERT_OK(actual.CopyTo(buf, sizeof(buf)));
  actual.Hash(buf, Digest::kLength);
  EXPECT_BYTES_EQ(actual.get(), expected.get(), Digest::kLength);
}

TEST(DigestTestCase, Split) {
  Digest actual, expected;
  actual.Init();
  size_t n = strlen(kZeroDigest);
  expected.Hash(kZeroDigest, n);
  for (size_t i = 1; i < n; ++i) {
    actual.Init();
    actual.Update(kZeroDigest, i);
    actual.Update(kZeroDigest + i, n - i);
    actual.Final();
    EXPECT_BYTES_EQ(actual.get(), expected.get(), Digest::kLength);
  }
}

TEST(DigestTestCase, CWrappers) {
  uint8_t buf[Digest::kLength];
  EXPECT_STATUS(digest_hash(nullptr, 0, buf, sizeof(buf) - 1), ZX_ERR_BUFFER_TOO_SMALL);
  ASSERT_OK(digest_hash(nullptr, 0, buf, sizeof(buf)));
  Digest expected;
  ASSERT_OK(expected.Parse(kZeroDigest));
  EXPECT_BYTES_EQ(buf, expected.get(), Digest::kLength);
  digest_t* digest = nullptr;
  ASSERT_OK(digest_init(&digest));
  expected.Hash(buf, sizeof(buf));
  digest_update(digest, buf, sizeof(buf));
  ASSERT_OK(digest_final(digest, buf, sizeof(buf)));
  EXPECT_BYTES_EQ(buf, expected.get(), Digest::kLength);
}

TEST(DigestTestCase, Equality) {
  Digest actual, expected;
  ASSERT_OK(expected.Parse(kZeroDigest));
  ASSERT_OK(actual.Parse(kZeroDigest));
  EXPECT_FALSE(actual == nullptr, "Does not equal NULL");
  EXPECT_TRUE(actual == actual, "Equals self");
  EXPECT_TRUE(actual == actual.get(), "Equals self.bytes_");
  EXPECT_TRUE(actual == expected, "Equals expected");
  EXPECT_TRUE(actual == expected.get(), "Equals expected.bytes_");
  EXPECT_TRUE(actual != nullptr, "Doesn't equal NULL");
  EXPECT_FALSE(actual != actual, "Doesn't not equal self");
  EXPECT_FALSE(actual != actual.get(), "Doesn't not equal self.bytes_");
  EXPECT_FALSE(actual != expected, "Doesn't not equal expected");
  EXPECT_FALSE(actual != expected.get(), "Doesn't not equal expected.bytes_");
}

TEST(DigestTestCase, Move) {
  const Digest uninitialized_digest;
  Digest digest1;

  {
    // Verify that digest1 is not valid, and that it's current digest value
    // is all zeros.  Verify that when move digest1 into digest2, that
    // both retain this property (not valid, digest full of zeros)
    EXPECT_EQ(digest1, uninitialized_digest);

    Digest digest2(std::move(digest1));
    EXPECT_BYTES_EQ(digest1.get(), uninitialized_digest.get(), Digest::kLength);
    EXPECT_BYTES_EQ(digest2.get(), uninitialized_digest.get(), Digest::kLength);
  }

  // Start a hash operation in digest1, verify that this does not update the
  // initial hash value.
  EXPECT_OK(digest1.Init());
  EXPECT_BYTES_EQ(digest1.get(), uninitialized_digest.get(), Digest::kLength);

  // Hash some nothing into the hash.  Again verify the digest is still
  // valid, but that the internal result is still full of nothing.
  digest1.Update(nullptr, 0);
  EXPECT_BYTES_EQ(digest1.get(), uninitialized_digest.get(), Digest::kLength);

  // Move the hash into digest2.  Verify that the context goes with the move
  // operation.
  Digest digest2(std::move(digest1));
  EXPECT_BYTES_EQ(digest1.get(), uninitialized_digest.get(), Digest::kLength);

  // Finish the hash operation started in digest1 which was moved into
  // digest2.  Verify that digest2 is no longer valid, but that the result is
  // what we had expected.
  Digest zero_digest;
  ASSERT_OK(zero_digest.Parse(kZeroDigest));
  digest2.Final();
  EXPECT_BYTES_EQ(digest2.get(), zero_digest.get(), Digest::kLength);

  // Move the result of the hash into a new digest3.  Verify that neither is
  // valid, but that the result was properly moved.
  Digest digest3(std::move(digest2));
  EXPECT_BYTES_EQ(digest2.get(), uninitialized_digest.get(), Digest::kLength);
  EXPECT_BYTES_EQ(digest3.get(), zero_digest.get(), Digest::kLength);
}

}  // namespace
